Conversation
There was a problem hiding this comment.
Pull request overview
Adds Android ANR detection/reporting (API 30+) and migrates crash/feedback uploads to a 3-step presigned S3 flow, along with new unit tests and a CI workflow to run them on PRs.
Changes:
- Introduce
AnrReporterto detect historical ANR exits and upload the OS-provided trace dump on next SDK init. - Add
ReportUploaderimplementing the 3-part S3 upload flow and refactorFeedbackClientto use it. - Add MockWebServer-based unit tests and a GitHub Actions workflow to run
:app:testDebugUnitTest.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
gradle/libs.versions.toml |
Adds MockWebServer version for unit test HTTP flow. |
app/build.gradle |
Enables unit test defaults and adds test dependencies (mockwebserver, org.json). |
app/src/main/java/com/bugsplat/android/ReportUploader.java |
Implements 3-step presigned S3 upload + supporting utilities. |
app/src/main/java/com/bugsplat/android/FeedbackClient.java |
Switches feedback posting to use ReportUploader and sets crash type metadata. |
app/src/main/java/com/bugsplat/android/AnrReporter.java |
Adds ANR detection via ApplicationExitInfo and uploads trace dumps. |
app/src/main/java/com/bugsplat/android/BugSplatBridge.java |
Triggers ANR check/report during SDK initialization. |
app/src/test/java/com/bugsplat/android/ReportUploaderTest.java |
Adds unit/integration-style tests for zipping, md5, and 3-step HTTP flow. |
app/src/test/java/com/bugsplat/android/FeedbackClientTest.java |
Adds tests for feedback report formatting and crash type metadata. |
example/src/main/res/layout/activity_main.xml |
Adds an “ANR” trigger button to the example UI. |
example/src/main/java/com/bugsplat/example/MainActivity.java |
Adds click handler to intentionally block main thread to trigger ANR. |
.github/workflows/tests.yml |
Adds PR CI job to run Android unit tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds BugSplat.hang() backed by a native infinite loop (jniHang) so the ANR thread dump includes a symbolicated C++ frame, enabling end-to-end testing of the symbolication pipeline against .sym files. Updates the example app's Trigger ANR button to call BugSplat.hang() instead of a Java busy-loop. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents the new ANR detection feature (Android 11+), including how it works, configuration, testing with BugSplat.hang(), and the Android 11+ requirement. Also updates the example app feature list to mention the ANR trigger button and custom attribute dialog. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Loads bugsplat.database, bugsplat.clientId, and bugsplat.clientSecret from the gitignored local.properties instead of hardcoding them in example/build.gradle. Fails the build with a clear error if bugsplat.database is not configured. Also switches bugsplatAppName and bugsplatAppVersion to be derived from android.defaultConfig.applicationId and versionName respectively, so there's a single source of truth for these values. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The per-ABI uploadBugSplatSymbols{BuildType}{Abi} tasks didn't declare
a dependency on the merge${BuildType}NativeLibs task that produces
merged_native_libs/. If the upload task ran before packageDebug (e.g.
when passed alongside installDebug on the command line), the native
libs directory didn't exist yet and the upload was silently skipped.
Adding dependsOn merge${BuildType}NativeLibs ensures the native libs
are always in place before the upload runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AGP 8.6 changed the intermediates layout to include the producing task
name in the path:
old: merged_native_libs/{buildType}/out/lib/{abi}/
new: merged_native_libs/{buildType}/merge{BuildType}NativeLibs/out/lib/{abi}/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the shared uploadBugSplatSymbols task (which used System.setProperty for buildType/abi, making it racy and Gradle-dedupe unsafe) with a proper per-ABI approach: - Extract upload logic into uploadSymbolsForAbi(buildType, abi) and resolveSymbolUploadExecutable() closures. - Each per-ABI task has its own doLast body that calls the helper with explicit parameters — no more System.setProperty. - AllAbis chains the per-ABI tasks with mustRunAfter so they run serially. The symbol-upload binary uses a shared temp dir, so parallel invocations aren't safe. - Delete the legacy uploadBugSplatSymbols task entirely. Fixes: AllAbis previously only uploaded one ABI due to Gradle deduplicating the shared task dependency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the stale Gradle example that showed hardcoded credentials in
ext{} with the local.properties approach. Adds notes on:
- AGP 8.6+ intermediate path layout
- Serial vs parallel execution (symbol-upload's shared temp dir)
- ABI-filter-aware skipping for single-ABI builds (e.g. AS running on
a single-ABI emulator)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI doesn't have a local.properties, so the top-level bugsplat.database check was failing the build. Adds resolution order: local.properties → -Pbugsplat.database=... → BUGSPLAT_DATABASE env var Both CI workflows (Tests, CodeQL) now pass BUGSPLAT_DATABASE=fred. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AnrReporter: - Only advance last_reported_anr_timestamp when upload succeeds so transient failures don't permanently skip the affected ANRs. - Process ANRs oldest-first (reverse the newest-first list from getHistoricalProcessExitReasons) and break on failure so newer ANRs are retried next launch without masking an older failure. - Shut down the one-shot executor after submitting the check so the worker thread doesn't linger. ReportUploader: - Fix FileInputStream leak in upload(File, ...) with try-with-resources. - URL-encode query string params in getCrashUploadUrl. - Use UTF-8 explicitly in readBody() and all multipart byte writes. - Accept any 2xx from S3 PUT (not just 200). - Add upload(Map<String, byte[]>, crashType, id) overload for multi-entry zips, plus matching createZip(Map) helper. FeedbackClient: - Restore attachment upload — attachments are now packed as real zip entries alongside feedback.txt, not just logged as filenames in the text report. This fixes a behavioral regression from the previous multipart feedback POST. Example app: - Move the "Trigger ANR" button label into strings.xml for i18n. Tests: - Clean up dead code in TestableReportUploader trailing-slash handling. - Update feedback attachment test to verify the new behavior (zip entry) rather than the old (name logged in feedback.txt). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
whoops what happened here?!?
There was a problem hiding this comment.
Right — that was from the feedback.txt era. Now resolved: feedback is emitted as feedback.json (verified against https://docs.bugsplat.com/introduction/development/web-services/user-feedback), with crashType=User.Feedback, crashTypeId=36, and the optional user / email / description / appKey / attributes fields carried on the commit request instead of baked into the body.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Conforms FeedbackClient and AnrReporter to the documented BugSplat crash/feedback upload API: https://docs.bugsplat.com/introduction/development/web-services/crash https://docs.bugsplat.com/introduction/development/web-services/user-feedback Changes: - FeedbackClient now emits a JSON feedback.json body with just title and optional description (not a human-readable text report). - crashType changed from "UserFeedback" / "Android ANR" to the dotted canonical forms "User.Feedback" / "Android.ANR". - Optional commit fields (user, email, description, appKey) now live on the commitS3CrashUpload multipart form, not baked into the body. - New `attributes` field support — pass a Map<String, String>, which gets JSON-encoded as the "attributes" commit field. - Added BugSplat.postFeedback / postFeedbackBlocking overloads that accept attributes. - Fixed commit field name casing: s3key → s3Key (matches docs). - ReportUploader.upload gains an (entries, crashType, crashTypeId, extraCommitFields) overload so callers can pass the optional fields. Tests updated: - feedbackBodyIsJson_withTitleAndDescription parses the JSON body - feedbackBody_omitsDescriptionWhenNullOrEmpty - feedbackBody_handlesNullTitleAsEmptyString - commitRequest_usesUserDotFeedbackCrashType - commitRequest_includesOptionalUserEmailAppKey - commitRequest_includesAttributesAsJsonString - commitRequest_omitsOptionalFieldsWhenNullOrEmpty Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a new 'Loading config from local.properties' section under Usage, and updates all init/postFeedback examples to reference BuildConfig.BUGSPLAT_DATABASE / APP_NAME / APP_VERSION instead of hardcoding 'fred' / 'my-app' / '1.0.0'. The same bugsplat.database value is reused by the symbol upload task, so there's now a single source of truth across runtime and build. Also adds a 'Custom Attributes' subsection under User Feedback showing how to pass the new attributes Map parameter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the opaque Map<String, String> extra-fields with a typed CommitOptions class that mirrors the documented commitS3CrashUpload multipart body 1-to-1 (crashType, crashTypeId, fullDumpFlag, appKey, description, user, email, internalIP, notes, processor, crashTime, attributes, crashHash). CommitOptions uses fluent setters on a package-private POJO — a real Builder would be more idiomatic if we ever expose it publicly, but for internal use the lighter form is fine. Simplifies ReportUploader: - Single entry point: upload(byte[] zipped, CommitOptions). Callers produce their own zips. - Small zip(name, bytes) helper for the common single-entry case (used by AnrReporter). - No more Map<String, ZipEntrySource> plumbing — FeedbackClient builds its zip inline with ZipOutputStream, streaming File attachments directly from disk so peak heap is ~zip size rather than attachment + zip size. Important on mobile where 100 MB max attachments could otherwise approach heap limits. - Drops the uniqueEntryName helper — callers are responsible for attachment filename collisions (ZipOutputStream throws if violated). AnrReporter: - Uses ReportUploader.zip(...) for its single-entry zip. - Replaces remaining "UTF-8" string literals with StandardCharsets.UTF_8 to avoid the checked-exception path. Tests updated to match the new API and expanded to cover all CommitOptions fields on the commit request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
ApplicationExitInfoAPI on Android 11+ (API 30+). On each SDK init, checks for unreported ANR exit reasons from previous sessions, reads the system thread dump, and uploads it to BugSplat. Thread dumps include full Java + native stacks with BuildIds for symbolication against .sym files.ReportUploader) replacing direct multipart POSTs:getCrashUploadUrl→ PUT to presigned S3 URL →commitS3CrashUpload. Used by bothAnrReporter(crashTypeId=37) andFeedbackClient(crashTypeId=36).ReportUploader(zip creation, MD5, HTTP flow with MockWebServer) andFeedbackClient(report formatting, field handling, crash type).Test plan
./gradlew :app:testDebugUnitTestpasses locally🤖 Generated with Claude Code